a tool for shared writing and social publishing
1import { z } from "zod";
2import {
3 PullRequest,
4 PullResponseV1,
5 VersionNotSupportedResponse,
6} from "replicache";
7import type { Fact } from "src/replicache";
8import { FactWithIndexes } from "src/replicache/utils";
9import type { Attribute } from "src/replicache/attributes";
10import { makeRoute } from "../lib";
11import type { Env } from "./route";
12
13// First define the sub-types for V0 and V1 requests
14const pullRequestV0 = z.object({
15 pullVersion: z.literal(0),
16 schemaVersion: z.string(),
17 profileID: z.string(),
18 cookie: z.any(), // ReadonlyJSONValue
19 clientID: z.string(),
20 lastMutationID: z.number(),
21});
22
23// For the Cookie type used in V1
24const cookieType = z.union([
25 z.null(),
26 z.string(),
27 z.number(),
28 z
29 .object({
30 order: z.union([z.string(), z.number()]),
31 })
32 .and(z.record(z.string(), z.any())), // ReadonlyJSONValue with order property
33]);
34
35const pullRequestV1 = z.object({
36 pullVersion: z.literal(1),
37 schemaVersion: z.string(),
38 profileID: z.string(),
39 cookie: cookieType,
40 clientGroupID: z.string(),
41});
42
43// Combined PullRequest type
44const PullRequestSchema = z.union([pullRequestV0, pullRequestV1]);
45
46export const pull = makeRoute({
47 route: "pull",
48 input: z.object({ pullRequest: PullRequestSchema, token_id: z.string() }),
49 handler: async ({ pullRequest, token_id }, { supabase }: Env) => {
50 let body = pullRequest;
51 if (body.pullVersion === 0) return versionNotSupported;
52 let { data, error } = await supabase.rpc("pull_data", {
53 token_id,
54 client_group_id: body.clientGroupID,
55 });
56 if (!data) {
57 console.log(error);
58
59 return {
60 error: "ClientStateNotFound",
61 } as const;
62 }
63
64 let facts = data.facts as {
65 attribute: string;
66 created_at: string;
67 data: any;
68 entity: string;
69 id: string;
70 updated_at: string | null;
71 version: number;
72 }[];
73 let publication_data = data.publications as {
74 description: string;
75 title: string;
76 }[];
77 let pub_patch = publication_data?.[0]
78 ? [
79 {
80 op: "put",
81 key: "publication_description",
82 value: publication_data[0].description,
83 },
84 {
85 op: "put",
86 key: "publication_title",
87 value: publication_data[0].title,
88 },
89 ]
90 : [];
91
92 let clientGroup = (
93 (data.client_groups as {
94 client_id: string;
95 client_group: string;
96 last_mutation: number;
97 }[]) || []
98 ).reduce(
99 (acc, clientRecord) => {
100 acc[clientRecord.client_id] = clientRecord.last_mutation;
101 return acc;
102 },
103 {} as { [clientID: string]: number },
104 );
105
106 return {
107 cookie: Date.now(),
108 lastMutationIDChanges: clientGroup,
109 patch: [
110 { op: "clear" },
111 { op: "put", key: "initialized", value: true },
112 ...(facts || []).map((f) => {
113 return {
114 op: "put",
115 key: f.id,
116 value: FactWithIndexes(f as unknown as Fact<Attribute>),
117 } as const;
118 }),
119 ...pub_patch,
120 ],
121 } as PullResponseV1;
122 },
123});
124
125const versionNotSupported: VersionNotSupportedResponse = {
126 error: "VersionNotSupported",
127 versionType: "pull",
128};